import 'dart:convert';
import 'dart:io';

import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:osc/common/osc_user_manager.dart';
import 'package:path_provider/path_provider.dart';


/// 请求方法.
class Method {
  static final String get = "GET";
  static final String post = "POST";
  static final String put = "PUT";
  static final String head = "HEAD";
  static final String delete = "DELETE";
  static final String patch = "PATCH";
}

///Http配置.
class HttpConfig {
  /// constructor.
  HttpConfig({
    this.status,
    this.code,
    this.msg,
    this.data,
//    this.options,
    this.baseOptions,
    this.pem,
    this.pKCSPath,
    this.pKCSPwd,
  });

  /// BaseResp [String status]字段 key, 默认：status.
  String status;

  /// BaseResp [int code]字段 key, 默认：errorCode.
  String code;

  /// BaseResp [String msg]字段 key, 默认：errorMsg.
  String msg;

  /// BaseResp [T data]字段 key, 默认：data.
  String data;

  /// Options.
  Options options;
  BaseOptions baseOptions;

  /// 详细使用请查看dio官网 https://github.com/flutterchina/dio/blob/flutter/README-ZH.md#Https证书校验.
  /// PEM证书内容.
  String pem;

  /// 详细使用请查看dio官网 https://github.com/flutterchina/dio/blob/flutter/README-ZH.md#Https证书校验.
  /// PKCS12 证书路径.
  String pKCSPath;

  /// 详细使用请查看dio官网 https://github.com/flutterchina/dio/blob/flutter/README-ZH.md#Https证书校验.
  /// PKCS12 证书密码.
  String pKCSPwd;
}

/// 单例 DioUtil.
/// debug模式下可以打印请求日志. DioUtil.openDebug().
/// dio详细使用请查看dio官网(https://github.com/flutterchina/dio).
class DioUtil {
  static final DioUtil _singleton = DioUtil._init();
  static Dio _dio;

  /// BaseResp [String status]字段 key, 默认：status.
  String _statusKey = "status";

  /// BaseResp [int code]字段 key, 默认：errorCode.
  String _codeKey = "errorCode";

  /// BaseResp [String msg]字段 key, 默认：errorMsg.
  String _msgKey = "errorMsg";

  /// BaseResp [T data]字段 key, 默认：data.
  String _dataKey = "result";

  String _successKey = "success";
  String _unAuthorizedRequestKey = "unAuthorizedRequest";
  String _errorKey = "error";

  static const String AppToken = "ccbf55b0eb2c55fd31634585b7f856aa9d9067e4";

  /// Options.
  Options _options = getDefOptions();
  BaseOptions _baseOptions = getBaseOptions();

  /// PEM证书内容.
  String _pem;

  /// PKCS12 证书路径.
  String _pKCSPath;

  /// PKCS12 证书密码.
  String _pKCSPwd;

  /// 是否是debug模式.
  static bool _isDebug = false;

  static CookieJar cookieJar = new CookieJar();
  CookieManager cookieManager = new CookieManager(cookieJar);
  PersistCookieJar persistCookieJar;
  List<Cookie> cookies;

  static DioUtil getInstance() {
    return _singleton;
  }

  factory DioUtil() {
    return _singleton;
  }

  DioUtil._init() {
    _dio = new Dio(_baseOptions);

    loadCookies();
    /// 代理设置，方便抓包来进行接口调节
//    setProxy();
  }

  /// 打开debug模式.
  static void openDebug() {
    _isDebug = true;
  }

  /// set Config.
  void setConfig(HttpConfig config) {
    _statusKey = config.status ?? _statusKey;
    _codeKey = config.code ?? _codeKey;
    _msgKey = config.msg ?? _msgKey;
    _dataKey = config.data ?? _dataKey;
    _mergeOption(config.baseOptions);
    _pem = config.pem ?? _pem;
    if (_dio != null) {
      _dio.options = _baseOptions;
      if (_pem != null) {
        //todo
        /*_dio.onHttpClientCreate = (HttpClient client) {
          client.badCertificateCallback =
              (X509Certificate cert, String host, int port) {
            if (cert.pem == _pem) {
              // 证书一致，则放行
              return true;
            }
            return false;
          };
        };*/
      }
      if (_pKCSPath != null) {
        //todo
        /*_dio.onHttpClientCreate = (HttpClient client) {
          SecurityContext sc = new SecurityContext();
          //file为证书路径
          sc.setTrustedCertificates(_pKCSPath, password: _pKCSPwd);
          HttpClient httpClient = new HttpClient(context: sc);
          return httpClient;
        };*/
      }
    }
  }

  loadCookies(){
    String tempPath;
    Future<Directory> tempDir = getApplicationDocumentsDirectory();
    tempDir.then((dir){
      tempPath = dir.path;
      try{
        persistCookieJar = new PersistCookieJar(dir: tempPath);
        Uri uri = Uri.parse(_baseOptions.baseUrl);
        CookieJar cj = cookieManager.cookieJar;
        if(cj != null){
          cookies = cj.loadForRequest(uri);
        }
        if(cookies == null || cookies.isEmpty){
          cookies = persistCookieJar.loadForRequest(uri);
        }
        if(cookies != null && cookies.isNotEmpty){
          _options.cookies = cookies;
          for(Cookie cookie in cookies){
            if(cookie.name.compareTo("_user_behavior_") == 0){
              if(cookie.expires.isAfter(DateTime.now())){
//                cj.saveFromResponse(uri, cookies);
                cookieManager.cookieJar.saveFromResponse(uri, cookies);
              } else {
                OscUserManager.instance.logout();
              }
            }
          }
          if(OscUserManager.instance.isLogin()) {
            _dio.interceptors.add(cookieManager);
          }
        } else {
//          UserManager.instance.setLogin(false);
        }
      } catch(err){
      }
    });
  }

  cleanCookies(){
    _dio.interceptors.remove(cookieManager);
    if(cookieManager != null){
      List<Cookie> list = new List();
      cookieManager.cookieJar.saveFromResponse(Uri.parse(_baseOptions.baseUrl), list);
    }
    if(persistCookieJar != null){
      persistCookieJar.deleteAll();
    }
    _dio.interceptors.add(cookieManager);
  }

  static void setProxy() {
    (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
        (HttpClient client) {
      client.findProxy = (uri) {
        //proxy all request to localhost:8888
        return "PROXY 192.168.2.144:8888";
      };
    };
  }

  Future<Response> request(String method, String path,
      {data, queryParameters, Options options, CancelToken cancelToken}) async {
    Response response;
    try{
      response = await _dio.request(path,
          data: data,
          queryParameters: queryParameters,
          options: _checkOptions(method, options),
          cancelToken: cancelToken);

      if(path.endsWith("apiv2/account_login")) {
        Uri uri = Uri.parse(response.request.baseUrl);
        cookieManager.onResponse(response);
        List<Cookie> cookies = cookieManager.cookieJar.loadForRequest(uri);
        List<Cookie> tempList = new List();
        if (cookies != null && cookies.isNotEmpty) {
          tempList.addAll(cookies);
          cookieManager.cookieJar.saveFromResponse(uri, tempList);
          _dio.interceptors.add(cookieManager);
          persistCookieJar.deleteAll();
          persistCookieJar.saveFromResponse(uri, tempList);
        }
      }
    } on DioError catch(err){
      response = err.response;
    }
    _printHttpLog(response);
    return response;
  }


  /// Download the file and save it in local. The default http method is "GET",you can custom it by [Options.method].
  /// [urlPath]: The file url.
  /// [savePath]: The path to save the downloading file later.
  /// [onProgress]: The callback to listen downloading progress.please refer to [OnDownloadProgress].
  /*Future<Response> download(
      String urlPath,
      savePath, {
        OnDownloadProgress onProgress,
        CancelToken cancelToken,
        data,
        Options options,
      }) {
    return _dio.download(urlPath, savePath,
        onProgress: onProgress,
        cancelToken: cancelToken,
        data: data,
        options: options);
  }*/

  /// decode response data.
  Map<String, dynamic> _decodeData(Response response) {
    if (response == null ||
        response.data == null ||
        response.data.toString().isEmpty) {
      return new Map();
    }
    return json.decode(response.data.toString());
  }

  /// check Options.
  Options _checkOptions(method, options) {
    if (options == null) {
      if(_options == null){
        options = new Options();
      } else {
        options = _options;
      }
    }
    options.method = method;
    return options;
  }

  /// merge Option.
  void _mergeOption(BaseOptions opt) {
    _baseOptions.method = opt.method ?? _baseOptions.method;
    _baseOptions.headers = (new Map.from(_baseOptions.headers))..addAll(opt.headers);
    _baseOptions.baseUrl = opt.baseUrl ?? _baseOptions.baseUrl;
    _baseOptions.connectTimeout = opt.connectTimeout ?? _baseOptions.connectTimeout;
    _baseOptions.receiveTimeout = opt.receiveTimeout ?? _baseOptions.receiveTimeout;
    _baseOptions.responseType = opt.responseType ?? _baseOptions.responseType;
    _baseOptions.extra = (new Map.from(_baseOptions.extra))..addAll(opt.extra);
    _baseOptions.contentType = opt.contentType ?? _baseOptions.contentType;
    _baseOptions.validateStatus = opt.validateStatus ?? _baseOptions.validateStatus;
    _baseOptions.followRedirects = opt.followRedirects ?? _baseOptions.followRedirects;
  }

  /// print Http Log.
  void _printHttpLog(Response response) {
    if (!_isDebug) {
      return;
    }
    try {
      String requestData = "";
      if(response.request.data != null && response.request.queryParameters != null){
        requestData = response.request.data.toString() + response.request.queryParameters.toString();
      } else if(response.request.data != null){
        requestData = response.request.data.toString();
      } else if(response.request.queryParameters != null){
        requestData = "{";
        response.request.queryParameters.forEach((key, value){
          requestData += "$key:$value,";
        });
        requestData += "}";
      } else {
        requestData = "null";
      }
      print("----------------Http Log----------------"
          + "\n[statusCode]:   " + response.statusCode.toString()
          + "\n[request   ]:   " + _getOptionsStr(response.request)
          + "\n[reqdata   ]: " + requestData
//          + "\n[header    ]: " + response.request.headers.toString()??"null"
//          + "\n[cookies   ] " + response.request.cookies.toString()
      );
      print("header: \n\n[header    ]: " + response.request.headers.toString()??"null");
      print("cookies: \n\n[cookies   ]: " + response.request.cookies.toString());
//      print("\n[cookies   ] " + response.request.cookies.toString());
//      print("\n[reqdata   ] " + response.request.data != null?response.request.data:response.request.queryParameters);
      _printDataStr("\n[response  ]: ", response.data);
    } catch (ex) {
      print("Http Log" + " error......");
    }
  }

  /// get Options Str.
  String _getOptionsStr(RequestOptions request) {
    return "method: " +
        request.method +
        "  baseUrl: " +
        request.baseUrl +
        "  path: " +
        request.path;
  }

  /// print Data Str.
  void _printDataStr(String tag, Object value) {
    String da = value.toString();
    bool first = true;
    while (da.isNotEmpty) {
      if (da.length > 512) {
        print(first?"[$tag  ]:   " + da.substring(0, 512) : da.substring(0, 512));
        da = da.substring(512, da.length);
      } else {
        print(first?"[$tag  ]:   " + da : da);
        da = "";
      }
      first = false;
    }
  }

  /// get dio.
  Dio getDio() {
    return _dio;
  }

  /// create new dio.
  static Dio createNewDio([BaseOptions options]) {
    options = options ?? getBaseOptions();
    Dio dio = new Dio(options);
    return dio;
  }

  /// get Def Options.
  static Options getDefOptions() {
    Options options = new Options();
//    options.contentType = ContentType.parse("application/x-www-form-urlencoded");
    options.contentType = ContentType.json;
    options.connectTimeout = 1000 * 50;
    options.receiveTimeout = 1000 * 60;
    options.headers = {"AppToken": AppToken};
    return options;
  }

  //getBaseOptions
  static BaseOptions getBaseOptions() {
    BaseOptions options = new BaseOptions();
    options.contentType = ContentType.json;
    options.connectTimeout = 1000 * 50;
    options.receiveTimeout = 1000 * 60;
    options.headers = {"AppToken": AppToken};
    return options;
  }
}